package com.dhz.common.id.redis;

import com.dhz.common.redis.RedisConnectionFactory;
import com.dhz.common.redis.RedisServerConf;
import com.dhz.common.redis.RedisTemplate;
import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Id生成器 - 方式一.
 *
 * 分片ID由业务中传入，如用户订单数据的ID根据用户ID做分片；则需传入UserId
 *
 * @author FY
 * @since 1.0
 */
public class IdGeneratorTemplate2 extends RedisTemplate {
    private static final Logger logger = LoggerFactory.getLogger(IdGeneratorTemplate.class);
    /** 尝试次数 */
    private static final int DEFAULT_MAX_ATTEMPTS = 5;

    private final int maximumAttempts;

    public IdGeneratorTemplate2() {
        this(DEFAULT_MAX_ATTEMPTS);
    }

    public IdGeneratorTemplate2(final int maximumAttempts) {
        this.maximumAttempts = maximumAttempts;
    }

    /**
     * 生产 tag 下的ID
     * @param tag 模块名称 | 表名称
     * @return ID value
     */
    public IdObject next(String tag) {
        return next(tag, 0);
    }

    /**
     * 生产 tag 下的ID
     * @param tag 模块名称 | 表名称
     * @param shared 分区编号
     * @return ID value
     */
    public IdObject next(String tag, long shared) {
        if (tag == null || "".equals(tag)) throw new IdGeneratorException("生成ID时必须设置 TAG 参数");
        RedisServerConf server = getFactory().server();
        Assert.notNull(server, "no id generator server can be use!!!");
        for (int i=0; i<maximumAttempts; ++i) {
            try {
                Optional<IdObject> optId = generatorId(server, tag, shared);
                // 如果有结果，直接返回
                if (optId.isPresent()) {
                    return optId.get();
                }
                // 重试等待,字数递增休眠时间，避免所有请求持续在Redis服务商运行
                Thread.sleep(i * i);
            } catch (Exception e) {
                // logger.warn("使用Redis服务生成ID失败.", e);
            }
        }
//        throw new IdGeneratorException(String.format("生成 {%s} 的 ID 失败", tag));
        // 运行到这里表示整个ID的生成都失败了，记录日志
        logger.error("在重试 {} 次后，没有成功生成ID", maximumAttempts);
        throw new IdGeneratorException("生成ID失败.");
    }

    private Optional<IdObject> generatorId(final RedisServerConf server, final String tag, final long shared) {
        Optional<IdResponse> optResponse = executeLuaScript(server, tag, shared);

        if (!optResponse.isPresent()) {
            return Optional.absent();
        }

        IdResponse response = optResponse.get();

        return Optional.of(new IdObject(response.timeSeconds, response.timeMicroseconds, response.logicalShardId, response.sequence));
    }

    /**
     * 提交Lua脚本及参数
     * @param server RedisServerConf
     * @param shared 分片基数
     * @return IdResponse
     */
    @SuppressWarnings("unchecked")
    private Optional<IdResponse> executeLuaScript(final RedisServerConf server, final String tag, final long shared) {
        // 要提交的参数: tag, max_sequence, size
        final String[] args = new String[]{tag, String.valueOf(shared)};
        JedisPool jedisPool = server.getPool();
        Jedis jedis = jedisPool.getResource();

        try {
            Optional<IdResponse> response = evalLuaSha(jedis, server.getLuaSha(), args);

            if (response.isPresent()) {
                return response;
            }

            // 若没有能够正确取到结果，则载入一次脚本
            server.loadLuaScript();

            return evalLuaSha(jedis, server.getLuaSha(), args);
        } catch (JedisConnectionException e) {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
                jedis = null;
            }
            // logger.error("Redis Generate Id error!", e);
            throw new IdGeneratorException("Redis Generate Id error!", e);
        } catch (IOException e) {
            // logger.error("Can't load Redis Id Generator Lua Script File with : {}", server.getLuaFilePath(), e);
            throw new IdGeneratorException("Redis Generate Id error!", e);
        } finally {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
            }
        }
    }

    /**
     * 执行LuaSha
     * @param jedis jedis实例
     * @param luaSha luaSha字符串调用lua脚本
     * @param args 传入脚本参数
     * @return 返回值
     */
    @SuppressWarnings("unchecked")
    private Optional<IdResponse> evalLuaSha(final Jedis jedis, final String luaSha, final String[] args) {
        return Optional.of(new IdResponse((List<Long>) jedis.evalsha(luaSha, args.length, args)));
    }

    static class IdResponse {
        /** 秒时间（unix时间戳） */
        private final long timeSeconds;
        /** 微秒时间（一秒内） */
        private final long timeMicroseconds;
        /** 逻辑分片ID */
        private final long logicalShardId;
        /** 序列号 */
        private final long sequence;

        public IdResponse(final List<Long> results) {
            this.timeSeconds = results.get(0);
            this.timeMicroseconds = results.get(1);
            this.logicalShardId = results.get(2);
            this.sequence = results.get(3);
        }
    }


}
